package com.yntx.usercenter.authentication.service.impl;

import cn.hutool.crypto.SecureUtil;
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.yntx.usercenter.authentication.dto.JwtPayloadDto;
import com.yntx.usercenter.authentication.service.TokenService;
import com.yntx.usercenter.common.config.TenantUserContextHolder;
import com.yntx.usercenter.common.exception.BizException;
import com.yntx.usercenter.common.util.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.text.ParseException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Collections;
import java.util.List;

/**
 * jwt令牌服务impl
 *
 * @author jielihaofeng
 * @date 2023-04-26 12:46
 */
@Slf4j
@Service
public class TokenServiceImpl implements TokenService {

    /**
     * 应用程序名称
     */
    @Value("${spring.application.name:user-center}")
    private String applicationName;

    /**
     * 令牌秘钥 对称加密方式
     */
    @Value("${jwt.token.secret:YNTXXTNY}")
    private String tokenSecret;

    @Override
    public String generateToken(JwtPayloadDto jwtPayloadDto) throws JOSEException {
        // todo:校验逻辑
        // 租户ID作为加密秘钥，没有则取公共配置
        String secret = StringUtils.defaultIfEmpty(jwtPayloadDto.getTenantId(), tokenSecret);
        return generateToken(JsonUtil.toJson(jwtPayloadDto), SecureUtil.md5(secret));
    }

    @Override
    public String generateToken(String payloadStr, String secret) throws JOSEException {
        // 创建JWS头，设置签名算法和类型
        JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.HS256).
                type(JOSEObjectType.JWT)
                .build();
        // 将负载信息封装到Payload中
        Payload payload = new Payload(payloadStr);
        // 创建JWS对象
        JWSObject jwsObject = new JWSObject(jwsHeader, payload);
        // 创建HMAC签名器
        JWSSigner jwsSigner = new MACSigner(secret);
        // 签名
        jwsObject.sign(jwsSigner);
        // 转字符串
        String token = jwsObject.serialize();
        log.info("#generateToken# payload:{}, secret:{}, token:{}", payloadStr, secret, token);
        return token;
    }

    @Override
    public JwtPayloadDto verifyToken(String token) throws JOSEException, ParseException {
        // 租户ID作为加密秘钥，没有则取用公共配置
        String secret = StringUtils.defaultIfEmpty(TenantUserContextHolder.get().getTenantId(), tokenSecret);
        return verifyToken(token, secret);
    }

    @Override
    public JwtPayloadDto verifyToken(String token, String secret) throws JOSEException, ParseException {
        // 从token中解析JWS对象
        JWSObject jwsObject = JWSObject.parse(token);
        // 创建HMAC验证器
        JWSVerifier jwsVerifier = new MACVerifier(SecureUtil.md5(secret));
        if (!jwsObject.verify(jwsVerifier)) {
            throw new BizException("token签名不合法！");
        }
        String payload = jwsObject.getPayload().toString();
        JwtPayloadDto payloadDto = JsonUtil.toBean(payload, JwtPayloadDto.class);
        if (payloadDto != null && payloadDto.getExp() != null && payloadDto.getExp() < System.currentTimeMillis()) {
            throw new BizException("token已过期！");
        }
        log.info("#verifyToken# token:{}, secret:{}, payload:{}", token, secret, payload);
        return payloadDto;
    }

    @Override
    public JwtPayloadDto getDefaultJwtPayloadDto(String username, String tenantId,Long systemId, List<String> authorities) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime exp = now.plusHours(2);
        JwtPayloadDto jwtPayloadDto = JwtPayloadDto.builder()
                .sub(applicationName)
                .iat(now.toInstant(ZoneOffset.of("+8")).toEpochMilli())
                .exp(exp.toInstant(ZoneOffset.of("+8")).toEpochMilli())
                .jti(RandomStringUtils.randomAlphabetic(16))
                .username(username)
                .tenantId(tenantId)
                .systemId(systemId)
                .authorities(CollectionUtils.isEmpty(authorities) ? Collections.singletonList("DEFAULT") : authorities)
                .build();
        log.info("#getDefaultJwtPayloadDto# username:{}, username:{}, authorities:{}, jwtPayloadDto:{}", username, tenantId, authorities, jwtPayloadDto);
        return jwtPayloadDto;
    }

}
